package com.test.mvcframework.utils;

import java.io.File;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.net.URISyntaxException;
import java.util.*;

/**
 * 反射工具类
 */
public class ReflectionUtils {
    /**
     * 获取指定包下含有指定注解的类
     *
     * @param packageName
     * @param interfaceType
     * @return
     */
    public static Set<Class<?>> getInterfaceClasses(String packageName, Class<?> interfaceType) {
        try {
            Class<?>[] classes = getClasses(packageName);
            Set<Class<?>> resultSet = new LinkedHashSet<>();
            for (Class<?> aClass : classes) {
                if (interfaceType.isAssignableFrom(aClass)) {
                    resultSet.add(aClass);
                }
            }
            return resultSet;
        } catch (ClassNotFoundException | IOException | URISyntaxException e) {
            e.printStackTrace();
            throw new RuntimeException("根据包名获取类出错");
        }
    }

    /**
     * 获取指定包下含有指定注解的类
     *
     * @param packageName
     * @param annotationClass
     * @return
     */
    public static Set<Class<?>> getTypeAnnotatedClasses(String packageName,
            Class<? extends Annotation> annotationClass) {
        try {
            Class<?>[] classes = getClasses(packageName);
            Set<Class<?>> resultSet = new LinkedHashSet<>();
            for (Class<?> aClass : classes) {
                if (aClass.getAnnotation(annotationClass) != null) {
                    resultSet.add(aClass);
                }
            }
            return resultSet;
        } catch (ClassNotFoundException | IOException | URISyntaxException e) {
            e.printStackTrace();
            throw new RuntimeException("根据包名获取类出错");
        }
    }

    public static Set<Method> getAnnotatedMethod(Class<?> typeClass,
            Class<? extends Annotation> annotationClass) {
        Set<Method> methodSet = new LinkedHashSet<>();
        Method[] methods = typeClass.getMethods();
        for (Method method : methods) {
            if (method.getAnnotation(annotationClass) != null) {
                methodSet.add(method);
            }
        }
        return methodSet;
    }

    public static Set<Field> getAnnotatedFields(Class<?> typeClass,
            Class<? extends Annotation> annotationClass) {
        Set<Field> fieldSet = new LinkedHashSet<>();
        Field[] fields = typeClass.getDeclaredFields();
        for (Field field : fields) {
            if (field.getAnnotation(annotationClass) != null) {
                fieldSet.add(field);
            }
        }
        return fieldSet;
    }

    public static <T extends Annotation> T convertAnnotationType(Annotation source,
            Class<T> destClass) throws NoSuchFieldException, IllegalAccessException {
        T targetAnnotation = source.annotationType().getAnnotation(destClass);
        assert targetAnnotation != null;
        Map<String, Object> sourceValuesMap = getAnnotationValuesMap(source);
        Map<String, Object> destValuesMap = getAnnotationValuesMap(targetAnnotation);
        destValuesMap.putAll(sourceValuesMap);
        return targetAnnotation;
    }

    private static Map<String, Object> getAnnotationValuesMap(Annotation annotation)
            throws NoSuchFieldException, IllegalAccessException {
        InvocationHandler invocationHandler = Proxy.getInvocationHandler(annotation);
        Field field = invocationHandler.getClass().getDeclaredField("memberValues");
        field.setAccessible(true);

        return (Map<String, Object>) field.get(invocationHandler);
    }

    /**
     * Scans all classes accessible from the context class loader which belong to the given package and subpackages.
     *
     * @param packageName The base package
     * @return The classes
     * @throws ClassNotFoundException
     * @throws IOException
     */
    private static Class<?>[] getClasses(String packageName)
            throws ClassNotFoundException, IOException, URISyntaxException {
        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
        assert classLoader != null;
        String path = packageName.replace('.', '/');
        String dirPath = classLoader.getResource(path).toURI().getPath();
        File dir = new File(dirPath);
        List<File> dirs = new ArrayList<>();
        dirs.addAll(Arrays.asList(dir.listFiles()));
        ArrayList<Class<?>> classes = new ArrayList<>();
        for (File directory : dirs) {
            classes.addAll(findClasses(directory, packageName + "." + directory.getName()));
        }
        return classes.toArray(new Class[classes.size()]);
    }

    /**
     * Recursive method used to find all classes in a given directory and subdirs.
     *
     * @param directory   The base directory
     * @param packageName The package name for classes found inside the base directory
     * @return The classes
     * @throws ClassNotFoundException
     */
    private static List<Class<?>> findClasses(File directory, String packageName)
            throws ClassNotFoundException {
        List<Class<?>> classes = new ArrayList<>();
        if (!directory.exists()) {
            return classes;
        }
        File[] files = directory.listFiles();
        if (files == null) {
            return classes;
        }
        for (File file : files) {
            if (file.isDirectory()) {
                assert !file.getName().contains(".");
                classes.addAll(findClasses(file, packageName + "." + file.getName()));
            } else if (file.getName().endsWith(".class")) {
                classes.add(Class.forName(packageName + '.' + file.getName()
                        .substring(0, file.getName().length() - 6)));
            }
        }
        return classes;
    }
}
